相信es6中的class(类)对我们来说并不陌生,在我的工作中,也用到了ES6这一特性,虽然一直在使用,但是并没有去认真了解过它,于是做了一些功课,在这里跟大家分享自己的一些心得和看法。
比较
在ES6以前,JS并没有给我们提供对类的支持,常用做法是用构造函数来模拟类的实现,通过将属性和方法定义在原型上将自身的属性共享给它的实例。
来看一个例子:1
2
3
4
5
6
7
8
9
10
11function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
let p = new Point(1, 2);
p.toString()
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念。通过class关键字,可以定义类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `( ${this.x}, ${this.y} )`;
}
}
let p = new Point(1,2)
p.toString()
//(1,2)
解释:
constructor:构造方法,
this: 表示实例对象,
toString: 实例的方法
constructor
constructor是类的默认方法,通过new 命令生成对象实例时,自动调用该方法,一个类必须有constructor方法,如果没有显示定义,会默认添加一个空的constructor方法1
2
3
4class Point{ } //等同于
class Point {
constructor () { }
}
constructor方法默认返回实例对象(this),如果我们指定返回另一个对象
1 | class Person { |
ES6 的(class)类,完全可以看作构造函数的另一种写法, 类的数据类型就是函数,类本身就指向构造函数
1 | typeof Point // "function" |
类必须使用new调用,否则会报错。
在定义类的时候,前面不需要加function关键字
this 指向
如果类的方法内部含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。1
2
3
4
5
6
7
8
9
10
11
12
13class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
类的实例对象
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
1 | //定义类 |
Class 的静态方法
所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。1
2
3
4
5
6
7
8class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
let foo = new Foo();
foo.classMethod() // TypeError: foo.classMethod is not a function
思考一下1
2
3
4
5
6
7
8
9
10
11
12
13class Foo {
static bar () {
this.baz();
}
static baz () {
console.log('hello');
}
baz () {
console.log('world');
}
}
Foo.bar() // ??
如果静态方法包含this关键字,这个this指的是类,而不是实例, 从这个例子还可以看出,静态方法可以与非静态方法重名。
所以这个例子最终打印出来的会是 hello.
Class 的静态属性和实例属性
静态属性
静态属性和静态方法都是一样,都是定义在类上,而不是实例对象上。
先来感受一下:1
2
3
4class Foo {
static prop = 1;
}
Foo.prop // 1
类的实例属性
eg:1
2
3
4
5
6
7class MyClass {
myProp = 42;
constructor() {
console.log(this.myProp); // 42
}
}
以前我们定义实例属性,只能写在constructor方法里面,像下面这样1
2
3
4
5
6
7
8class Index extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
}
类的继承
Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 父类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static hello() {
console.log('hello world');
}
}
// 子类
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y); // 调用父类的constructor(x, y)
this.color = color; // 正确
}
}
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
父类的静态方法,也会被子类继承
类的私有属性和私有方法
私有属性
ES6 不支持私有属性。目前,有一个提案,为class加了私有属性。方法是在属性名之前,使用#表示
之所以要引入一个新的前缀#表示私有属性,是因为js没有private关键字
另外,Ruby 语言使用@表示私有属性,ES6 没有用@符号而使用#,是因为@已经被留给了 Decorator
1 | class Point { |
私有方法
私有方法是常见需求,但 ES6 不提供,只能通过其他方法模拟实现
第一种方法:1
2
3
4
5
6
7
8
9
10
11class Widget {
// 公有方法
foo (baz) {
this._bar(baz);
}
// 私有方法
_bar(baz) {
return this.snaf = baz;
}
// ...
}
第二种方法
还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值1
2
3
4
5
6
7
8
9
10
11
12
13const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
// ...
};